package com.chouxiaozi.farmework.context;

import com.chouxiaozi.farmework.annotation.WsAutowired;
import com.chouxiaozi.farmework.annotation.WsController;
import com.chouxiaozi.farmework.annotation.WsService;
import com.chouxiaozi.farmework.aop.WsAopProxy;
import com.chouxiaozi.farmework.aop.WsCglibAopProxy;
import com.chouxiaozi.farmework.aop.WsJdkDynamicAopProxy;
import com.chouxiaozi.farmework.aop.config.WsAopConfig;
import com.chouxiaozi.farmework.aop.support.WsAdvisedSupport;
import com.chouxiaozi.farmework.beans.WsBeanFactory;
import com.chouxiaozi.farmework.beans.WsBeanWrapper;
import com.chouxiaozi.farmework.beans.factory.config.WsBeanDefinition;
import com.chouxiaozi.farmework.beans.factory.support.WsBeanDefinitionReader;
import com.chouxiaozi.farmework.beans.support.WsDefaultListableBeanFactory;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/***
 *
 * 原本是接口，有多种实现像ClassPathXmlApplicationContext、AnnotationConfigApplicationContext
 * 本次手写简单一些，直接定义成类
 */
public class WsApplicationContext extends WsDefaultListableBeanFactory implements WsBeanFactory {

    private String[] configLoactions;

    WsBeanDefinitionReader reader;

    //单例的IOC容器缓存
    private Map<String,Object> factoryBeanObjectCache = new HashMap<String, Object>();
    //通用的IOC容器
    private Map<String,WsBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, WsBeanWrapper>();

    public WsApplicationContext(String... configLoactions) {
        this.configLoactions = configLoactions;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    protected void refresh() throws Exception {
        //1、定位，定位配置文件
        reader = new WsBeanDefinitionReader(configLoactions);
        //2、加载，加载配置文件，扫描相关的类，把它们封装成BeanDefinition
        List<WsBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
        //3、注册，把配置信息放到容器里面（伪IOC容器）
        doRegisterBeanDefinition(beanDefinitions);
        //4、把不是延迟加载的类  提前初始化 （lazyInit = false）
        doAutowrited();

    }

    private void doAutowrited() {
        for (Map.Entry<String, WsBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 注册，把配置信息放到容器里面
     * @param beanDefinitions
     * @throws Exception
     */
    private void doRegisterBeanDefinition(List<WsBeanDefinition> beanDefinitions) throws Exception {
        for (WsBeanDefinition beanDefinition : beanDefinitions) {
            if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
            }
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
        //到这里为止，容器初始化完毕
    }

    /**
     * 依赖注入，从这里开始，通过读取 BeanDefinition 中的信息
     * 然后，通过反射机制创建一个实例并返回
     * Spring 做法是，不会把最原始的对象放出去，会用一个 BeanWrapper 来进行一次包装
     * 装饰器模式： 1、保留原来的 OOP 关系 2、我需要对它进行扩展，增强（为了以后 AOP 打基础）
     * 进行对象实例化和DI注入
     * @param beanName
     * @return
     */
    @Override
    public Object getBean(String beanName) {
        WsBeanDefinition beanDefinition = super.beanDefinitionMap.get(beanName);
        if(null == beanDefinition){
            return null;
        }
        //循环依赖；初始化和注入是分开的
        //1、初始化 读取注册的类信息，初始化类对象保存到真正的ioc容器中
        Object instance = instantiateBean(beanDefinition);
        if(null == instance){
            return null;
        }

        WsBeanWrapper beanWrapper = new WsBeanWrapper(instance);
        this.factoryBeanInstanceCache.put(beanName,beanWrapper);

        //2、注入
        populateBean(beanName, new WsBeanDefinition(), beanWrapper);

        return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    }

    public Object getBean(Class<?> clazz){
        return getBean(clazz.getSimpleName());
    }

    /**
     * 注入
     * @param beanName
     * @param wsBeanDefinition
     */
    private void populateBean(String beanName, WsBeanDefinition wsBeanDefinition, WsBeanWrapper wsBeanWrapper) {
        Object instance = wsBeanWrapper.getWrappedInstance();
        Class clazz = wsBeanWrapper.getWrappedClass();
        if(!(clazz.isAnnotationPresent(WsController.class) || clazz.isAnnotationPresent(WsService.class))){
            return;
        }
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if(!field.isAnnotationPresent(WsAutowired.class)){ continue;}
            WsAutowired wsAutowired = field.getAnnotation(WsAutowired.class);
            String autoWiredBeanName = wsAutowired.value();
            if("".equals(autoWiredBeanName)){
                autoWiredBeanName = field.getType().getName();
            }
            field.setAccessible(true);
            try {
                if(this.factoryBeanInstanceCache.get(autoWiredBeanName) == null){
                    Object bean = getBean(field.getType().getName());
                }
                field.set(instance, this.factoryBeanInstanceCache.get(autoWiredBeanName).getWrappedInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 初始化
     * @param wsBeanDefinition
     * @return
     */
    private Object instantiateBean(WsBeanDefinition wsBeanDefinition) {
        Object instance = null;
        String className = wsBeanDefinition.getBeanClassName();
        try {
            //默认全是单例
            if(this.factoryBeanObjectCache.containsKey(className)){
                instance = this.factoryBeanObjectCache.get(className);
            }else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();

                WsAdvisedSupport config = instantionAopConfig(wsBeanDefinition);
                config.setTargetClass(clazz);
                config.setTarget(instance);

                //符合PointCut的规则的话，闯将代理对象
                if(config.pointCutMatch()) {
                    instance = createProxy(config).getProxy();
                }
                this.factoryBeanObjectCache.put(className, instance);
                this.factoryBeanObjectCache.put(wsBeanDefinition.getFactoryBeanName(), instance);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return instance;
    }

    private WsAopProxy createProxy(WsAdvisedSupport config) {

        Class targetClass = config.getTargetClass();
        if(targetClass.getInterfaces().length > 0){
            return new WsJdkDynamicAopProxy(config);
        }
        return new WsCglibAopProxy(config);
    }

    private WsAdvisedSupport instantionAopConfig(WsBeanDefinition wsBeanDefinition) {
        WsAopConfig config = new WsAopConfig();
        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new WsAdvisedSupport(config);
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new  String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount(){
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig(){
        return this.reader.getConfig();
    }

    public static void main(String[] args) {
        WsApplicationContext wsApplicationContext = new WsApplicationContext("classpath:application.properties");
        Object myAction = wsApplicationContext.getBean("modifyService");
        System.out.println(myAction);
    }
}
